#include "StdAfx.h"
#include "Stroke.h"
#include "Calculator.h"
#include "Curve.h"
#include "NeighborEnumerator.h"
#include <GdiPlus.h>
#include <vector>
#include <sstream>
#include <assert.h>

int Stroke::sNextID = 1000;

Stroke::Stroke()
	: mDots(), mIsClosed(false), mFrontEta(0), mBackEta(0),
	mID(++sNextID), mNearestDot(mDots.end()),
	mAverageLineWidth(0), mAverageBrightness(0),
	mPath(NULL)
{
}
	
Stroke::Stroke(const Stroke& inOtherStroke)
	: mDots(inOtherStroke.mDots), mIsClosed(inOtherStroke.mIsClosed),
	mFrontEta(inOtherStroke.mFrontEta), mBackEta(inOtherStroke.mBackEta),
	mID(inOtherStroke.GetID()),	mNearestDot(mDots.end()),
	mAverageLineWidth(inOtherStroke.mAverageLineWidth), 
	mAverageBrightness(inOtherStroke.mAverageBrightness),
	mPath(NULL)
{
	if (NULL != inOtherStroke.mPath)
	{
		mPath = inOtherStroke.mPath->Clone();
	}
	mBounds = inOtherStroke.mBounds;
	mBoundsExtended = inOtherStroke.mBoundsExtended;

	const DotCollection& otherDots = inOtherStroke.mDots;
	if (inOtherStroke.mNearestDot != otherDots.end())
	{
		mNearestDot = mDots.begin();
		std::advance(mNearestDot, 
			std::distance(otherDots.begin(), inOtherStroke.mNearestDot));
	}
}
Stroke::~Stroke()
{
	delete mPath;
	mPath = NULL;
}

bool Stroke::Initialize(int inX, int inY, Calculator& ioCalculator) 
{
	mDots.clear();
	ContourDot dot(inX, inY);
	if (!dot.PlaceAtInitialPosition(ioCalculator))
	{
		return false;
	}
	// mark pixels of the just created contour dot.
	ioCalculator.NoteToAnchor();
	mFrontEta = ioCalculator.GetMinEta();
	mBackEta = ioCalculator.GetMaxEta();
	mDots.push_back(dot);

	bool wasExtendedForward = false;
	bool isStrokeExtensibleBackward = ioCalculator.IsMiddlePoint();
	mIsClosed = false;

	while (!mIsClosed)
	{
		ContourDot dot(mDots.back());
		if (!dot.PlaceAtNeighborPosition(ioCalculator, true))
		{
			// the dot chain can not be extended
			break;
		}
		if (ioCalculator.IsForwardOverlapping())
		{
			mIsClosed = (3 < mDots.size() && dot.CanBeNeighbor(mDots.front()));
		}

		ioCalculator.MarkOccupiedTheCurrent();
		ioCalculator.NoteToCurrent();

		wasExtendedForward = true;
		mBackEta = ioCalculator.GetMaxEta();
		mDots.push_back(dot);	
	}

	if (wasExtendedForward)
	{
		// mark the contour dot at the end of stroke
		ioCalculator.MarkOccupiedTheCurrent();
		ioCalculator.ChangeTheAnchorState(0);
	}
	bool wasExtendedBackward = false;

	if (isStrokeExtensibleBackward)
	{
		while (!mIsClosed)
		{
			ContourDot dot(mDots.front());
			if (!dot.PlaceAtNeighborPosition(ioCalculator, false))
			{
				break;
			}
			if (ioCalculator.IsBackwardOverlapping())
			{
				mIsClosed = (3 < mDots.size() && dot.CanBeNeighbor(mDots.back()));
			}

			if (wasExtendedBackward || !wasExtendedForward)
			{
				ioCalculator.MarkOccupiedTheCurrent();
			}
			else
			{
				ioCalculator.ChangeTheAnchorState(1);
			}
			wasExtendedBackward = true;
			ioCalculator.NoteToCurrent();

			mFrontEta = ioCalculator.GetMinEta();
			mDots.push_front(dot);
		}
	}
	if (wasExtendedBackward  || !wasExtendedForward)
	{
		ioCalculator.MarkOccupiedTheCurrent();
	}
	else
	{
		ioCalculator.ChangeTheAnchorState(1);
	}

	if (mIsClosed)
	{
		// one dot will be unnecessary if the two dots (the first and the last)
		// are very close one to another
		const ContourDot& dot1 = mDots.front();
		const ContourDot& dot2 = mDots.back();
		float dx = dot2.GetX() - dot1.GetX();
		float dy = dot2.GetY() - dot1.GetY();

		// use some magic constant to define what is this: too close
		float minDistance = dot1.GetLineWidth() * 0.4f;
		if (dx * dx + dy * dy < minDistance * minDistance)
		{
			mDots.erase(mDots.begin());
		}
	}

	mNearestDot = mDots.end();

	return (1 <= mDots.size());
}

int Stroke::GetID() const
{
	return mID;
}

bool Stroke::IsClosed() const
{
	return mIsClosed;
}

void Stroke::Finalize(Calculator& ioCalculator)
{
	RECT r, re;
	float lineWidthTotal = 0;
	float brightnessTotal = 0;
	int dotCount = 0;

	for (DotIterator it = mDots.begin(); it != mDots.end(); ++it)
	{
		it->Finalize(ioCalculator);

		lineWidthTotal += it->GetLineWidth();
		brightnessTotal += it->GetBrightness();
		++dotCount;

		// calculate stroke bounds
		LONG xCenter = LONG(it->GetX());
		LONG yCenter = LONG(it->GetY());

		double delta = it->GetOuterHalfsize() * (::fabs(it->GetVx()) + ::fabs(it->GetVy()));
		LONG left = LONG(::floor(it->GetX() - delta));
		LONG right = LONG(::ceil(it->GetX() + delta));
		LONG top = LONG(::floor(it->GetY() - delta));
		LONG bottom = LONG(::ceil(it->GetY() + delta));

		if (mDots.begin() == it)
		{
			r.left = r.right = xCenter;
			r.top = r.bottom = yCenter;
			re.left = left;
			re.right = right;
			re.top = top;
			re.bottom = bottom;
		}
		else
		{
			r.left = min(r.left, xCenter);
			r.right = max(r.right, xCenter);
			r.top = min(r.top, yCenter);
			r.bottom = max(r.bottom, yCenter);

			re.left = min(re.left, left);
			re.right = max(re.right, right);
			re.top = min(re.top, top);
			re.bottom = max(re.bottom, bottom);
		}
	}
	::InflateRect(&r, 2, 2);
	::InflateRect(&re, 2, 2);
	mBounds = r;
	mBoundsExtended = re;

	mAverageLineWidth = lineWidthTotal / dotCount;
	mAverageBrightness = brightnessTotal / dotCount;
	InitializePath();
}

int Stroke::GetDotCount() const
{
	return mDots.size();
}

void Stroke::DrawCurve(Gdiplus::Graphics& inGraphics, const Gdiplus::Pen* inPen) const
{
	inGraphics.DrawPath(inPen, GetPathPtr());
}

void Stroke::DrawDotCenters(Gdiplus::Graphics& inGraphics, 
	const Gdiplus::SolidBrush* inBrush, const Gdiplus::Pen* inPen,
	float inRadius) const
{
	for (ConstDotIterator it = mDots.begin(); it != mDots.end(); ++it)
	{
		Gdiplus::REAL r = float(it == mNearestDot ? 1.5 * inRadius : inRadius);
		Gdiplus::RectF rect(float(it->GetX() - r), float(it->GetY() - r), 
			2 * r, 2 * r);
		inGraphics.FillEllipse(inBrush, rect);
		inGraphics.DrawEllipse(inPen, rect);
	}
}

void Stroke::DrawDotRects(Gdiplus::Graphics& inGraphics, const Gdiplus::Pen* inPen) const
{
	for (ConstDotIterator it = mDots.begin(); it != mDots.end(); ++it)
	{
		Gdiplus::GraphicsPath path;
		it->AddOuterShapeToGraphicsPath(path);
		inGraphics.DrawPath(inPen, &path);
	}
}

void Stroke::DrawDotInnerShapes(Gdiplus::Graphics& inGraphics, const Gdiplus::Pen* inPen) const
{
	for (ConstDotIterator it = mDots.begin(); it != mDots.end();)
	{
		float etaMin = mDots.begin() == it ? mFrontEta : -(it->GetOuterHalfsize());
		const ContourDot& dot = *it++;
		float etaMax = mDots.end() == it ? mBackEta : dot.GetOuterHalfsize();

		Gdiplus::GraphicsPath path;
		dot.AddInnerShapeToGraphicsPath(path, etaMin, etaMax);
		inGraphics.DrawPath(inPen, &path);
	}
}

void Stroke::DrawDotSideColors(Gdiplus::Graphics& inGraphics) const
{
	for (ConstDotIterator it = mDots.begin(); it != mDots.end();)
	{
		float etaMin = mDots.begin() == it ? mFrontEta : -(it->GetOuterHalfsize());
		const ContourDot& dot = *it++;
		float etaMax = mDots.end() == it ? mBackEta : dot.GetOuterHalfsize();

		dot.DrawSideColors(inGraphics, etaMin, etaMax);
	}
}

bool Stroke::FindNearestDot(float inX, float inY, float inAreaEnlargement,
	bool& outSelectedDotChanged)
{
	ConstDotIterator foundDot = mDots.end();
	for (ConstDotIterator it = mDots.begin(); it != mDots.end(); ++it)
	{
		float dx = inX - it->GetX();
		float dy = inY - it->GetY();
		float eta = dx * it->GetVx() + dy * it->GetVy();
		float xi = -dx * it->GetVy() + dy * it->GetVx();

		if (::fabs(eta) < it->GetOuterHalfsize() + inAreaEnlargement && 
			it->GetMinXi() - inAreaEnlargement < xi && 
			xi < it->GetMaxXi() + inAreaEnlargement)
		{
			foundDot = it;
			break;
		}
	}

	if (foundDot != mNearestDot)
	{
		mNearestDot = foundDot;
		outSelectedDotChanged = true;
	}

	return mDots.end() != mNearestDot;
}

void Stroke::UnselectNearestDot()
{
	mNearestDot = mDots.end();
}

void Stroke::GetHighlightedInfo(std::wostringstream& output) const
{
	if (mDots.end() != mNearestDot)
	{
		output.setf(std::ios_base::fixed);
		output.precision(3);
		output << "; dot eps=" << mNearestDot->GetApproximationPrecision()
			<< " k=" << mNearestDot->GetCurvature();
		if (mDots.begin() == mNearestDot)
		{
			output << " *";
		}
	}
}

const RECT& Stroke::GetBounds(bool inExtended) const
{
	return (inExtended ? mBoundsExtended : mBounds);
}

bool Stroke::IsIntersecting(const RECT& inRect, bool inUseExtendedBounds) const
{
	const RECT& bounds = GetBounds(inUseExtendedBounds);
	return bounds.left < inRect.right && bounds.right > inRect.left &&
		bounds.top < inRect.bottom && bounds.bottom > inRect.top;
}

bool Stroke::IsIntersecting(const Gdiplus::RectF& inRect, bool inUseExtendedBounds) const
{
	const RECT& bounds = GetBounds(inUseExtendedBounds);
	return float(bounds.left) < inRect.GetRight() && 
		float(bounds.right) > inRect.GetLeft() &&
		float(bounds.top) < inRect.GetBottom() &&
		float(bounds.bottom) > inRect.GetTop();
}

Curve Stroke::CreateCurve(int inPlace) const
{
	assert(!mDots.empty());

	if (2 > mDots.size())
	{
	}

	if (kBeforeFront == inPlace)
	{
		ConstDotIterator i = mDots.begin();
		return Curve(mDots.front(), *(++i), this, kBeforeFront);
	}
	if (kAfterEnd == inPlace)
	{
		DotCollection::const_reverse_iterator ri = mDots.rbegin();
		return Curve(mDots.back(), *(++ri), this, kAfterEnd);
	}

	assert(mDots.size() >= 2 && 0 <= inPlace && inPlace < (int)mDots.size() - 1);

	ConstDotIterator i = mDots.begin();
	std::advance(i, inPlace);
	ConstDotIterator i1 = i;

	return Curve(*i, *(++i1), this, inPlace);
}

void Stroke::RegisterEndDots(NeighborEnumerator& outEnumerator) const
{
	outEnumerator.Add(mDots.front(), this, kBeforeFront);
	outEnumerator.Add(mDots.back(), this, kAfterEnd);
}

const ContourDot* Stroke::GetContourDotAt(int inPlace) const
{
	if (mDots.empty())
	{
		return NULL;
	}
	if (kBeforeFront == inPlace)
	{
		return &mDots.front();
	}
	if (kAfterEnd == inPlace)
	{
		return &mDots.back();
	}
	return NULL;
}

float Stroke::GetTerminatingEta(int inPlace) const
{
	return (kBeforeFront == inPlace ? mFrontEta : mBackEta);
}

float Stroke::AverageLineWidth() const
{
	return mAverageLineWidth;
}

float Stroke::AverageBrightness() const
{
	return mAverageBrightness;
}

const Gdiplus::GraphicsPath* Stroke::GetPathPtr() const
{
	return mPath;
}

//////////////////////////////////////////////////////////
void Stroke::InitializePath()
{
	delete mPath;
	mPath = new Gdiplus::GraphicsPath;

	if (mFrontEta < 0 && !IsClosed())
	{
		Curve curve(this, kBeforeFront);
		curve.AddToPath(mPath);
	}
	ConstDotIterator it = mDots.begin();
	ConstDotIterator prevIt = it++;
	while (mDots.end() != it)
	{
		Curve curve(*prevIt, *it);
		curve.AddToPath(mPath);
		prevIt = it++;
	}

	if (IsClosed())
	{
		Curve curve(mDots.back(), mDots.front());
		curve.AddToPath(mPath);
		mPath->CloseFigure();
	}
	else if (0 < mBackEta)
	{
		Curve curve(this, kAfterEnd);
		curve.AddToPath(mPath);
	}
}

